package com.google.code.mochaccino.framework.crypto.hash;

/*
 * Copyright 2012 Claude Houle claude.houle@gmail.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import com.google.code.mochaccino.framework.crypto.EncryptionProvider;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;

/**
 * The Hash Algo enum provides all available hashing algorithm
 * to the application
 */
public enum HashAlgo {

	GOST3411( "GOST3411", EncryptionProvider.getBouncyCastleName() ),
	MD2( "MD2", EncryptionProvider.getBouncyCastleName() ),
	MD4( "MD4", EncryptionProvider.getBouncyCastleName() ),
	MD5( "MD5", EncryptionProvider.getBouncyCastleName() ),
	RIPEMD128( "RIPEMD128", EncryptionProvider.getBouncyCastleName() ),
	RIPEMD160( "RIPEMD160", EncryptionProvider.getBouncyCastleName() ),
	RIPEMD256( "RIPEMD256", EncryptionProvider.getBouncyCastleName() ),
	RIPEMD320( "RIPEMD320", EncryptionProvider.getBouncyCastleName() ),
	SHA1( "SHA1", EncryptionProvider.getBouncyCastleName() ),
	SHA224( "SHA224", EncryptionProvider.getBouncyCastleName() ),
	SHA256( "SHA256", EncryptionProvider.getBouncyCastleName() ),
	SHA384( "SHA384", EncryptionProvider.getBouncyCastleName() ),
	SHA512( "SHA512", EncryptionProvider.getBouncyCastleName() ),
	TIGER( "Tiger", EncryptionProvider.getBouncyCastleName() ),
	WHIRLPOOL( "WHIRLPOOL", EncryptionProvider.getBouncyCastleName() );

	/** The Provider */
	private String provider;

	/** The Type */
	private String type;

	/** Constructor */
	private HashAlgo( String type, String provider ) {
		this.type = type;
		this.provider = provider;
	}

	/** Hash the data */
	public String hash( byte[] in, byte[] salt, int iterationCount ) throws IOException {
		return hash( new ByteArrayInputStream( in ), salt, iterationCount );
	}

	/** Hash an input stream */
	public String hash( InputStream in ) throws IOException {
		return hash( in, null, 0 );
	}

	/** Hash an input stream including a salt and iteration count */
	public String hash( InputStream in, byte[] salt, int iterationCount ) throws IOException {
		byte[] hash = hashAsBytes( in, salt, iterationCount );
		StringBuilder hexString = new StringBuilder();
		for ( int i = 0; i < hash.length; i++ ) {
			hexString.append( Integer.toHexString( 0xFF & hash[i] ) );
		}
		return hexString.toString();
	}

	/** Hash */
	public String hash( String in ) throws IOException {
		return hash( in.getBytes() );
	}

	/** Hash */
	public String hash( byte[] in ) throws IOException {
		return hash( new ByteArrayInputStream( in ), null, 0 );
	}

	/** Hash */
	public String hash( String in, byte[] salt, int iterationCount ) throws IOException {
		return hash( new ByteArrayInputStream( in.getBytes() ), salt, iterationCount );
	}

	/** Hash */
	public byte[] hashAsBytes( InputStream in ) throws IOException {
		return hashAsBytes( in, null, 0 );
	}

	/** Hash */
	public byte[] hashAsBytes( InputStream in, byte[] salt, int iterationCount ) throws IOException {
		try {
			MessageDigest digest = MessageDigest.getInstance( type, provider );

			// Make sure iteration count is at least 8
			iterationCount = Math.max( 8, iterationCount );

			// Reset Digest
			digest.reset();

			// Read a part of the input stream
			int sizeRead = -1;
			byte[] buf = new byte[1024];
			while ( (sizeRead = in.read( buf, 0, 1024 )) != -1 ) {
				digest.update( buf, 0, sizeRead );
			}

			// Digest with no salt
			byte[] data = digest.digest();

			// We got a salt
			if ( salt != null ) {
				// Do the iteration
				for ( int i = 0; i < iterationCount; i++ ) {
					// Reset Digest
					digest.reset();

					// Update with salt
					digest.update( salt );

					// Update with data
					digest.update( data );

					// Do Digest
					data = digest.digest();
				}
			}

			// Return result
			return data;
		} catch ( NoSuchAlgorithmException e ) {
			throw new IOException( e );
		} catch ( NoSuchProviderException e ) {
			throw new IOException( e );
		}
	}

}
